package org.com.sec.noncritical.enigma;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

/**
 * Implements the coding/decoding functionality of a three rotors Swiss K rotors
 * enigma machine.
 *
 * @author See <a href="the Google Code page of the easy-utils
 * library">http://code.google.com/p/easy-utils/</a> for collaborators and other
 * information.
 */
public final class EnigmaMachine {

    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", MAP_RI = "PEZUOHXSCVFMTBGLRINQJWAYDK", MAP_RII = "ZOUESYDKFWPCIQXHMVBLGNJRAT", MAP_RIII = "EHRVXGAOBQUSIMZFLYNWKTPDJC", MAP_REFLECTOR = "IMETCGFRAYSQBZXWLHKDVUPOJN";
    private static Rotor rotorI, rotorII, rotorIII;
    private static char initialPosI, initialPosII, initialPosIII;
    private static final HashMap<Character, Character> steckers = new HashMap<>(), reflector = new HashMap<>();
    private static final HashMap<Integer, Character> nonLetters = new HashMap<>();

    /**
     * Getter for {@link EnigmaMachine#ALPHABET}.
     *
     * @return {@link String} {@link EnigmaMachine#ALPHABET}.
     */
    protected static String getAlphabet() {
        return EnigmaMachine.ALPHABET;
    }

    /**
     * Main method.
     *
     * @param args {@link ArrayList} of {@link String} The command line
     * arguments. The first parameter is compulsory and represents the message
     * to handle. The second one is compulsory too and it must represent the
     * rotors starting letters, separated by commas. The third one is optional
     * and represents the steckers. Each stecker reallocation must be separated
     * by a comma.
     * @return {@link String} The codified message.
     */
    public static String execute(ArrayList<String> args) {
        if (args.size() < 2) {
            throw new IllegalArgumentException("Not enough arguments. Usage: EnigmaMachine <message><starting positions>[<steckers>]");
        }

        for (int i = 0; i < args.get(0).length(); i++) {
            if (!EnigmaMachine.ALPHABET.contains(args.get(0).toUpperCase().charAt(i) + "")) {
                EnigmaMachine.nonLetters.put(i, args.get(0).charAt(i));
            }
        }

        EnigmaMachine.initializeReflector();

        StringTokenizer initialPositions = new StringTokenizer(args.get(1).toUpperCase(), ",");
        EnigmaMachine.initialPosI = initialPositions.nextToken().charAt(0);
        EnigmaMachine.initialPosII = initialPositions.nextToken().charAt(0);
        EnigmaMachine.initialPosIII = initialPositions.nextToken().charAt(0);
        EnigmaMachine.rotorI = new Rotor(EnigmaMachine.initialPosI, EnigmaMachine.MAP_RI);
        EnigmaMachine.rotorII = new Rotor(EnigmaMachine.initialPosII, EnigmaMachine.MAP_RII);
        EnigmaMachine.rotorIII = new Rotor(EnigmaMachine.initialPosIII, EnigmaMachine.MAP_RIII);

        String cleanString = args.get(0).toUpperCase();
        StringBuilder finalDirtyString = new StringBuilder("");

        if (args.size() == 3) {
            StringTokenizer steckerPairs = new StringTokenizer(args.get(2).toUpperCase(), ",");
            while (steckerPairs.hasMoreTokens()) {
                String currentToken = steckerPairs.nextToken();
                EnigmaMachine.steckers.put(currentToken.charAt(0), currentToken.charAt(1));
                EnigmaMachine.steckers.put(currentToken.charAt(1), currentToken.charAt(0));
            }

            cleanString = EnigmaMachine.clear(cleanString);

        }

        for (int i = 0; i < cleanString.length(); i++) {
            if (EnigmaMachine.nonLetters.get(i) != null) {
                finalDirtyString.append(EnigmaMachine.nonLetters.get(i));
            } else {
                finalDirtyString.append(EnigmaMachine.convert(cleanString.charAt(i)));
            }
        }

        if (args.size() == 3) {
            return EnigmaMachine.clear(finalDirtyString.toString());
        } else {
            return finalDirtyString.toString();
        }
    }

    /**
     * Performs the corresponding modifications on a string in order to go
     * through the steckers.
     *
     * @param message {@link String} The message that travels through the
     * steckers.
     * @return {@link String} The message after passing through the steckers.
     */
    private static String clear(String message) {
        if (EnigmaMachine.steckers.isEmpty()) {
            return message;
        }

        StringBuilder cleanMessage = new StringBuilder("");

        for (int i = 0; i < message.length(); i++) {
            if (EnigmaMachine.steckers.containsKey(message.charAt(i))) {
                cleanMessage.append(EnigmaMachine.steckers.get(message.charAt(i)));
            } else {
                cleanMessage.append(message.charAt(i));
            }
        }

        return cleanMessage.toString();
    }

    /**
     * Fills the reflector with the static corresponding data (it won't be
     * modified). The configuration is similar to the UKW-K one from the Swiss K
     * model.
     */
    private static void initializeReflector() {
        for (int i = 0; i < EnigmaMachine.ALPHABET.length(); i++) {
            EnigmaMachine.reflector.put(EnigmaMachine.ALPHABET.charAt(i), EnigmaMachine.MAP_REFLECTOR.charAt(i));
        }
    }

    /**
     * Performs the whole travel including the reflector and back (just prior to
     * the steckers for a letter with the current rotors positions.
     *
     * @param letter {@link Character} The letter to use.
     * @return {@link Character} The corresponding letter after the whole
     * travel.
     */
    private static Character convert(Character letter) {
        return EnigmaMachine.reverseApplyRightRotorAndRotate(EnigmaMachine.reverseApplyMiddleRotor(EnigmaMachine.reverseApplyLeftRotor(EnigmaMachine.applyReflector(EnigmaMachine.applyLeftRotor(EnigmaMachine.applyMiddleRotor(EnigmaMachine.applyRightRotor(letter)))))));
    }

    /**
     * Applies the right rotor mutation. Performs no rotation since it'll be
     * performed when the data comes back from the reflector.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character applyRightRotor(Character letter) {
        return EnigmaMachine.rotorIII.translate(letter);
    }

    /**
     * Applies the middle rotor mutation. Performs no rotation since it'll be
     * performed when the data comes back from the reflector.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character applyMiddleRotor(Character letter) {
        return EnigmaMachine.rotorII.translate(letter);
    }

    /**
     * Applies the left rotor mutation. Performs no rotation since it'll be
     * performed when the data comes back from the reflector.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character applyLeftRotor(Character letter) {
        return EnigmaMachine.rotorI.translate(letter);
    }

    /**
     * Applies the reflector mutation. Performs no rotation since the reflector
     * is static.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The reflected letter.
     */
    private static Character applyReflector(Character letter) {
        return EnigmaMachine.reflector.get(letter);
    }

    /**
     * Applies the left rotor reverse mutation. Performs no rotation since it'll
     * be performed when the data comes back from the reflector.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character reverseApplyLeftRotor(Character letter) {
        return EnigmaMachine.rotorI.reverseTranslate(letter);
    }

    /**
     * Applies the middle rotor reverse mutation. Performs no rotation since
     * it'll be performed when the data comes back from the reflector.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character reverseApplyMiddleRotor(Character letter) {
        return EnigmaMachine.rotorII.reverseTranslate(letter);
    }

    /**
     * Applies the right rotor reverse mutation. Performs a rotation.
     *
     * @param letter {@link Character} The letter that comes into this rotor.
     * @return {@link Character} The brand new letter correponding to the one
     * that came in, after passing through the rotor.
     */
    private static Character reverseApplyRightRotorAndRotate(Character letter) {
        Character newLetter;

        newLetter = EnigmaMachine.rotorIII.reverseTranslate(letter);
        EnigmaMachine.moveRotors();

        return newLetter;
    }

    /**
     * Rotates the proper rotors. This method should be used only when a travel
     * is finished (not counting the steckers step).
     */
    private static void moveRotors() {
        EnigmaMachine.rotorIII.move();
        if (EnigmaMachine.rotorIII.getCurrentlySelectedLetter() == EnigmaMachine.initialPosIII) {
            EnigmaMachine.rotorII.move();
            if (EnigmaMachine.rotorII.getCurrentlySelectedLetter() == EnigmaMachine.initialPosII) {
                EnigmaMachine.rotorI.move();
            }
        }
    }
}